package perf.parse.valgrind;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Date;

import perf.data.Run;

public class Parser {

	private Run data = new Run( new Date() );
	private ObjectState srcTracker = new ObjectState( data );
	private ObjectState dstTracker = new ObjectState( srcTracker, data );
	private Decompressor decomp = new Decompressor();
	/** Used for position compression*/
	private int lastpos;
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		
		Parser parser = new Parser( );
		
		if (args.length != 1) {			
			System.out.println( "Invalid number of arguments: " + args.length );
			return;
		}		
		try {
			File f = new File( args[0] );
			parser.parse( f );
		} catch (FileNotFoundException fnfe) {
			System.out.println( "File not found: " + args[0] );
			return;
		}
		
		
		long stop = System.currentTimeMillis();
		
		System.out.println( parser.toString() );
		System.out.println( "Time spent: " + (stop - start) );
	}
	
	public Run parse( File f ) throws FileNotFoundException {
		LineNumberReader rdr;

		setRundate( new Date( f.lastModified() ) );
		rdr = new LineNumberReader( new FileReader( f ) );
		parsefile( rdr );
		return data;
	}
	
	private void setRundate( Date rundate ) {
		srcTracker.setRundate( rundate );
	}
	
	private void parsefile( LineNumberReader reader ) {
		String line = "";

		if ( ! reader.markSupported() ) {
			throw new RuntimeException( "Stream marking not supported" );
		}
		try {
			parseHeader( reader );

			while ( (line = reader.readLine()) != null ) {
				if ( line.length() > 0 ) {
					char c = line.charAt( 0 );

					if ( c == 'o' || c == 'f' ) {
						parseObject( line );
					} else if ( c == 'c' ) {
						//a call is always followed by a second line
						//specifying some call costs
						parseCall( line, reader );
					} else if ( c >= '0' && c <= '9' ) {
						parseCost( line );
					} else if ( c == '+' || c == '-' || c == '*') {
						parseCost( line );
					} else if ( ! line.startsWith( "totals:" ) ) {
						throw new RuntimeException( "Invalid first character in string " + line );
					}
				}
			}
		} catch (Exception e) {
			System.out.println( "Error occured during read of line: \"" + line + "\" linenum:" + reader.getLineNumber() );
			System.out.println( "Error message: " + e.getMessage() );
			e.printStackTrace();
		}
	}
	
	private void parseHeader( LineNumberReader reader ) throws IOException {
		String line;
		int idx;
		
		while ( (line = reader.readLine()) != null ) {
			idx = line.indexOf( ':' );
			
			if ( idx != -1 ) {
				String tag = line.substring( 0, idx );
				String val = line.substring( idx + 2 );
				//System.out.println( tag + "=\"" + val + "\"" );
				srcTracker.setDescription( tag, val );
			} else if ( line.length() == 0 ) {
				//skip this line as it is empty
			} else {
				reader.reset();
				return;
			}
			
			//we set the read ahead limit to only 100 characters as the
			//mark will only be on the previous line
			reader.mark( 100 );
		}		
	}
	
	private void parseCost( String line ) {
		//fist character is the + or - or * sign
		String [] nums = line.split( " " );
		
		lastpos = decomp.getInt(nums[0], lastpos);
		
		srcTracker.setLineCosts( lastpos, parseLongs( nums, 1 ) );
	}
	
	private void parseCall( String line, LineNumberReader reader ) throws IOException {
		//we know that the line starts with 'c' and
		//the alternatives are: cob, cfi, cfn, calls
		//we'll use the third letter to distinguish between
		//them.
		char c = line.charAt( 2 );

		switch (c) {
		//cob - call to object
		case 'b' : dstTracker.setObject( decomp.getVal( line, 4 ) ); break;
		//cfi - call to file
		case 'i' : dstTracker.setFile( decomp.getVal( line, 4 ) ); break;
		//cfn - call to function
		case 'n' : dstTracker.setFunction( decomp.getVal( line, 4 ) ); break;
		//call - the call cost (that continues on the following line
		case 'l' : 
			parseCallCosts(	line.substring( 6 ), reader.readLine() );
			break;
		default : throw new RuntimeException( "Invalid line: " + line );
		}
	}
	
	private void parseCallCosts( String l1, String l2 ) {
		//fist line contains the "call count" and "destination position"
		String[] split = l1.split( " " );
		if ( split.length != 2 ) {
			throw new RuntimeException( );
		}
		
		long callCount = Long.parseLong( split[0] );
		//for some reason, the call destination doesn't 
		//update the lastpos
		int destLine = decomp.getInt( split[1], lastpos );

		//second line contains the "source position" and "inclusive call cost"
		split = l2.split( " " );
		if ( split.length != 2 ) {
			throw new RuntimeException( "Invalid cost specification" );
		}

		//same as srcLine
		lastpos = decomp.getInt( split[0], lastpos );

		long[] costs = parseLongs( split, 1 );
		
		srcTracker.setCallCosts(lastpos, destLine, 
				dstTracker.getFunction(), callCount, costs);
		dstTracker.reset();
	}
	
	private long[] parseLongs( String[] split, int start ) {
		long[] arr = new long[split.length - start];
		
		for (int i = 0; i < arr.length; i++ ) {
			arr[i] = Long.parseLong( split[i+start] );
		}
		
		return arr;
	}
	
	private void parseObject( String line ) {
		//we know that it starts with 'o' or 'f' and that
		//there are five alternatives: ob, fl, fn, fi, fe
		//they are all two letters long
		char c = line.charAt( 1 );
		//switching on second character of the tag.
		switch (c) {
			//ob - object
			case 'b' : srcTracker.setObject( decomp.getVal( line, 3 ) ); break;
			//fl - sourcefile
			case 'l' : srcTracker.setFile( decomp.getVal( line, 3 ) ); break;
			//fn - function
			case 'n' : srcTracker.setFunction( decomp.getVal( line, 3 ) ); break;
			//fi - inline start
			case 'i' : srcTracker.setInline( decomp.getVal( line, 3 ) ); break;
			//fe - inline end
			case 'e' : srcTracker.setFile( decomp.getVal( line, 3 ) ); break;
			default : throw new RuntimeException( "Invalid tag: " 
					+ line.substring( 0, 2 ) );
		}
	}
		
	public String toString( ) {
		return data.toString();
	}
}
